package com.atguigu.yygh.hosp.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.yygh.common.exception.YyghException;
import com.atguigu.yygh.hosp.repository.ScheduleRepository;
import com.atguigu.yygh.hosp.service.DepartmentService;
import com.atguigu.yygh.hosp.service.HospitalService;
import com.atguigu.yygh.hosp.service.ScheduleService;
import com.atguigu.yygh.model.hosp.BookingRule;
import com.atguigu.yygh.model.hosp.Department;
import com.atguigu.yygh.model.hosp.Hospital;
import com.atguigu.yygh.model.hosp.Schedule;
import com.atguigu.yygh.vo.hosp.BookingScheduleRuleVo;
import com.atguigu.yygh.vo.hosp.ScheduleOrderVo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.format.DateTimeFormat;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author mgx
 * @version 1.0
 * @description: TODO
 * @date 2022/8/27 17:50
 */
@Service
public class ScheduleServiceImpl implements ScheduleService {

    @Autowired
    private ScheduleRepository scheduleRepository;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private HospitalService hospitalService;

    @Autowired
    private DepartmentService departmentService;

    @Override
    public void save(Map<String, Object> paramMap) {
        // 把map转化为pojo对象
        Schedule schedule = JSONObject.parseObject(JSONObject.toJSONString(paramMap), Schedule.class);
        // 根据三个参数: hoscode depcode hosScheduleId判断是否存在已经存在排班信息
        Schedule platformSchedule = scheduleRepository.findByHoscodeAndDepcodeAndHosScheduleId(schedule.getHoscode(), schedule.getDepcode(), schedule.getHosScheduleId());
        if (platformSchedule == null) {
            // 新增
            schedule.setIsDeleted(0);
            schedule.setUpdateTime(new Date());
            schedule.setCreateTime(new Date());
            // id会自动生成
            scheduleRepository.save(schedule);
        } else {
            // 修改
            schedule.setId(platformSchedule.getId());
            schedule.setUpdateTime(new Date());
            schedule.setCreateTime(platformSchedule.getCreateTime());
            schedule.setIsDeleted(platformSchedule.getIsDeleted());
            scheduleRepository.save(schedule);
        }
    }

    @Override
    public Page<Schedule> getSchedulePage(Map<String, Object> paramMap) {
        // 获取hoscode
        String hoscode = (String) paramMap.get("hoscode");
        // 获取分页信息
        int pageNum = Integer.parseInt((String) paramMap.get("page"));
        int size = Integer.parseInt((String) paramMap.get("limit"));
        // 根据hoscode查询所有的排班信息
        Schedule schedule = new Schedule();
        schedule.setHoscode(hoscode);
        Example<Schedule> example = Example.of(schedule);
        Pageable page = PageRequest.of(pageNum - 1, size, Sort.by("createTime").ascending());
        Page<Schedule> all = scheduleRepository.findAll(example, page);
        return all;
    }

    @Override
    public void remove(Map<String, Object> paramMap) {
        // 获取医院编号和排班编号
        String hoscode = (String) paramMap.get("hoscode");
        String hosScheduleId = (String) paramMap.get("hosScheduleId");

        // 根据这两个信息查询
        Schedule platformSchedule = scheduleRepository.findByHoscodeAndHosScheduleId(hoscode, hosScheduleId);

        if (platformSchedule != null) {
            scheduleRepository.deleteById(platformSchedule.getId());
        } else {
            throw new YyghException(201, "该医院的排班信息不存在");
        }
    }

    // 排班信息分页查询
    @Override
    public Map<String, Object> page(Integer pageNum, Integer pageSize, String hoscode, String depcode) {
        Map<String, Object> map = new HashMap<>();

        // 使用聚合：使用MongoTemplate
        /**
         *  聚合条件：
         *      1.根据日期分组
         *      2.按照日期排序
         *      3.分页条件
         */
        Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode);
        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(criteria),
                Aggregation.group("workDate")
                        .first("workDate").as("workDate")
                        .sum("reservedNumber").as("reservedNumber")
                        .sum("availableNumber").as("availableNumber")
                        .count().as("docCount"),
                Aggregation.sort(Sort.Direction.ASC, "workDate"),
                Aggregation.skip((pageNum - 1) * pageSize),
                Aggregation.limit(pageSize)
        );
        /**
         * 参数说明:
         *      第一个参数： 聚合条件
         *      第二个参数：输入类型的字节码文件
         *      第三个参数：输出类型的字节码文件
         */
        AggregationResults<BookingScheduleRuleVo> aggregate = mongoTemplate.aggregate(aggregation, Schedule.class, BookingScheduleRuleVo.class);
        // 当前页的数据
        List<BookingScheduleRuleVo> mappedResults = aggregate.getMappedResults();

        // 我们需要是周几
        for (BookingScheduleRuleVo bookingScheduleRuleVo : mappedResults) {
            Date workDate = bookingScheduleRuleVo.getWorkDate();
            // 转化周几进行赋值
            String dayOfWeek = getDayOfWeek(new DateTime(workDate));
            bookingScheduleRuleVo.setDayOfWeek(dayOfWeek);
        }

        // 再聚合查询一次，不带分页
        Aggregation aggregation2 = Aggregation.newAggregation(
                Aggregation.match(criteria),
                Aggregation.group("workDate")
        );
        AggregationResults<BookingScheduleRuleVo> aggregate2 = mongoTemplate.aggregate(aggregation2, Schedule.class, BookingScheduleRuleVo.class);

        // 返回数据
        map.put("list", mappedResults);
        map.put("total", aggregate2.getMappedResults().size());

        // 根据医院编码查询医院名称前端需要用到
        Hospital hospital = hospitalService.getHospitalInfoByHoscode(hoscode);
        Map<String, Object> baseMap = new HashMap<>();
        baseMap.put("hosname", hospital.getHosname());
        map.put("baseMap", baseMap);
        return map;
    }

    @Override
    public List<Schedule> detail(String hoscode, String depcode, String workDate) {
        // 转化一下workdate
        Date date = new DateTime(workDate).toDate();
        // 条件查询符合信息的排班详情
        List<Schedule> scheduleList = scheduleRepository.findByHoscodeAndDepcodeAndWorkDate(hoscode, depcode, date);

        //把得到list集合遍历，向设置其他值：医院名称、科室名称、日期对应星期
        scheduleList.stream().forEach(item -> {
            this.packageSchedule(item);
        });
        return scheduleList;
    }

    @Override
    public Map<String, Object> getSchedulePageByCondition(String hoscode, String depcode, Integer pageNum, Integer limit) {
        // 获取医院信息
        Hospital hospital = hospitalService.getHospitalInfoByHoscode(hoscode);
        if (hospital == null) {
            throw new YyghException(20001, "该医院信息不存在");
        }
        // 获取预约规则
        BookingRule bookingRule = hospital.getBookingRule();

        IPage<Date> page = this.getDateList(pageNum, limit, bookingRule);
        // dateList得到的是当前页的日期列表
        List<Date> dateList = page.getRecords();

        Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode).and("workDate").in(dateList);

        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(criteria),
                Aggregation.group("workDate").first("workDate").as("workDate")
                        .count().as("docCount")
                        .sum("reservedNumber").as("reservedNumber")
                        .sum("availableNumber").as("availableNumber"),
                Aggregation.sort(Sort.Direction.ASC, "workDate")
        );
        AggregationResults<BookingScheduleRuleVo> aggregate = mongoTemplate.aggregate(aggregation, Schedule.class, BookingScheduleRuleVo.class);
        List<BookingScheduleRuleVo> mappedResults = aggregate.getMappedResults();

        // 转化成map -> key对应的是我们当前天的日期， value对应的是我们当天的排班信息
        Map<Date, BookingScheduleRuleVo> collect = mappedResults.stream().collect(Collectors.toMap(BookingScheduleRuleVo::getWorkDate, BookingScheduleRuleVo -> BookingScheduleRuleVo));

        // 创建一个列表存放当前页所有排班信息
        List<BookingScheduleRuleVo> bookingScheduleRuleVoList = new ArrayList<>();

        int len = dateList.size();
        for (int i = 0; i < len; i++) {
            Date date = dateList.get(i);
            BookingScheduleRuleVo bookingScheduleRuleVo = collect.get(date);
            if (bookingScheduleRuleVo == null) {
                bookingScheduleRuleVo = new BookingScheduleRuleVo();
                bookingScheduleRuleVo.setDocCount(0);
                bookingScheduleRuleVo.setAvailableNumber(-1);
                bookingScheduleRuleVo.setReservedNumber(0);
            }
            bookingScheduleRuleVo.setWorkDate(date);
            bookingScheduleRuleVo.setWorkDateMd(date);
            bookingScheduleRuleVo.setStatus(0);
            bookingScheduleRuleVo.setDayOfWeek(this.getDayOfWeek(new DateTime(date)));

            // 第一页第一条进行特判 -- 判断当前时间是不是已经过了当前的预约的截止时间
            if (i == 0 && pageNum == 1) {
                DateTime stopTime = this.getDateTime(new Date(), bookingRule.getStopTime());
                if (stopTime.isBeforeNow()) {
                    bookingScheduleRuleVo.setStatus(-1); //过最后截止时间说明停止挂号
                }
            }

            // 对最后一页的最后一条进行特判 -- 要给它即将放号的状态
            if (i == (len - 1) && pageNum == page.getPages()) {
                bookingScheduleRuleVo.setStatus(1); // 设置即将放号
            }

            bookingScheduleRuleVoList.add(bookingScheduleRuleVo);
        }

        Map<String, Object> map = new HashMap<>();
        map.put("total", page.getTotal());
        map.put("list", bookingScheduleRuleVoList);
        Map<String, Object> baseMap = new HashMap<>();
        //医院名称
        baseMap.put("hosname", hospitalService.getHospitalInfoByHoscode(hoscode).getHosname());
        //科室
        Department department = departmentService.getDepartment(hoscode, depcode);
        //大科室名称
        baseMap.put("bigname", department.getBigname());
        //科室名称
        baseMap.put("depname", department.getDepname());
        //月
        baseMap.put("workDateString", new DateTime().toString("yyyy年MM月"));
        //放号时间
        baseMap.put("releaseTime", bookingRule.getReleaseTime());
        //停号时间
        baseMap.put("stopTime", bookingRule.getStopTime());
        map.put("baseMap", baseMap);

        return map;
    }

    @Override
    public Schedule getScheduleInfo(String scheduleId) {
        Schedule schedule = scheduleRepository.findById(scheduleId).get();
        this.packageSchedule(schedule);
        return schedule;
    }

    @Override
    public ScheduleOrderVo getScheduleOrderVo(String scheduleId) {
        // 根据id获取排班信息
        Schedule schedule = scheduleRepository.findById(scheduleId).get();
        ScheduleOrderVo scheduleOrderVo = new ScheduleOrderVo();
        BeanUtils.copyProperties(schedule, scheduleOrderVo);

        Hospital hospital = hospitalService.getHospitalInfoByHoscode(schedule.getHoscode());

        scheduleOrderVo.setHosname(hospital.getHosname());
        scheduleOrderVo.setDepname(departmentService.getDepName(schedule.getHoscode(), schedule.getDepcode()));
        scheduleOrderVo.setReserveDate(schedule.getWorkDate());
        scheduleOrderVo.setReserveTime(schedule.getWorkTime());
        // 退号需要提前的天数
        Integer quitDay = hospital.getBookingRule().getQuitDay();
        // 退号的时间 -- 时分
        String stopTime = hospital.getBookingRule().getStopTime();
        // 根据当前的workDate和规则 计算出退号的截止时间
        DateTime quitDateTime = this.getDateTime(new DateTime(schedule.getWorkDate()).plusDays(quitDay).toDate(), stopTime);
        scheduleOrderVo.setQuitTime(quitDateTime.toDate());

        // 设置一下当天排班预约的截止时间以便做判断
        Date stopDate = this.getDateTime(schedule.getWorkDate(), hospital.getBookingRule().getStopTime()).toDate();
        scheduleOrderVo.setStopTime(stopDate);
        return scheduleOrderVo;
    }

    @Override
    public boolean updateAvailableNumber(String scheduleId, Integer availableNumber) {
        Schedule schedule = scheduleRepository.findById(scheduleId).get();
        schedule.setAvailableNumber(availableNumber);
        schedule.setUpdateTime(new Date());
        scheduleRepository.save(schedule);
        return true;
    }

    @Override
    public void cancelSchedule(String scheduleId) {
        // 找到排班信息
        Schedule schedule = scheduleRepository.findByHosScheduleId(scheduleId);
        Integer availableNumber = schedule.getAvailableNumber();
        availableNumber += 1;
        schedule.setAvailableNumber(availableNumber);
        scheduleRepository.save(schedule);
    }

    private IPage getDateList(Integer pageNum, Integer limit, BookingRule bookingRule) {
        // 获取预约周期
        Integer cycle = bookingRule.getCycle();
        // 获取预约起始时间
        DateTime releaseTime = this.getDateTime(new Date(), bookingRule.getReleaseTime());
        // 判断此时此刻是不是已经过了当天的预约起始时间,过了就cycle+1
        if (releaseTime.isBeforeNow()) {
            cycle += 1;
        }
        // 获取预约周期内的所有日期列表 -- 年月日
        List<Date> dateList = new ArrayList<>();
        for (int i = 0; i < cycle; i++) {
            // 获取当前日期
            DateTime dateTime = new DateTime().plusDays(i);
            // 转化为字符串
            String dateTimeString = dateTime.toString("yyyy-MM-dd");
            // 转化为date
            dateList.add(new DateTime(dateTimeString).toDate());
        }

        // 获取当前分页的所有日期列表
        List<Date> pageDateList = new ArrayList<>();
        int start = (pageNum - 1) * limit;
        int end = start + limit;
        if (end > dateList.size()) {
            end = dateList.size();
        }
        for (int i = start; i < end; i++) {
            pageDateList.add(dateList.get(i));
        }
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<Date> iPage = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNum, limit, dateList.size());
        iPage.setRecords(pageDateList);
        return iPage;
    }

    /**
     * 将Date日期（yyyy-MM-dd HH:mm）转换为DateTime
     */
    public DateTime getDateTime(Date date, String timeString) {
        // 拼接
        String dateTimeString = new DateTime(date).toString("yyyy-MM-dd") + " " + timeString;
        DateTime dateTime = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(dateTimeString);
        return dateTime;
    }

    //封装排班详情其他值 医院名称、科室名称、日期对应星期
    private void packageSchedule(Schedule schedule) {
        //设置医院名称
        schedule.getParam().put("hosname", hospitalService.getHospitalInfoByHoscode(schedule.getHoscode()).getHosname());
        //设置科室名称
        schedule.getParam().put("depname", departmentService.getDepName(schedule.getHoscode(), schedule.getDepcode()));
        //设置日期对应星期
        schedule.getParam().put("dayOfWeek", this.getDayOfWeek(new DateTime(schedule.getWorkDate())));
    }

    /**
     * 根据日期获取周几数据
     *
     * @param dateTime
     * @return
     */
    private String getDayOfWeek(DateTime dateTime) {
        String dayOfWeek = "";
        switch (dateTime.getDayOfWeek()) {
            case DateTimeConstants.SUNDAY:
                dayOfWeek = "周日";
                break;
            case DateTimeConstants.MONDAY:
                dayOfWeek = "周一";
                break;
            case DateTimeConstants.TUESDAY:
                dayOfWeek = "周二";
                break;
            case DateTimeConstants.WEDNESDAY:
                dayOfWeek = "周三";
                break;
            case DateTimeConstants.THURSDAY:
                dayOfWeek = "周四";
                break;
            case DateTimeConstants.FRIDAY:
                dayOfWeek = "周五";
                break;
            case DateTimeConstants.SATURDAY:
                dayOfWeek = "周六";
            default:
                break;
        }
        return dayOfWeek;
    }
}
